import os
import shutil
import zipfile
from copy import deepcopy, copy
from pathlib import Path
from time import time
from typing import (Union, Optional, Sequence, Generator, Callable,
                    Literal, Iterator, Any)
from .log import logger

try:
    from filetype import guess, types as file_types
except ModuleNotFoundError:
    logger.warning(f"请安装 filetype (pip install filetype) 模块"
                   f"，否则"
                   f" File.type_obj File.file_type() File.type File.suffix"
                   f" 将无法使用")
    file_types = Any
try:
    from send2trash import send2trash
except ModuleNotFoundError:
    logger.warning(f"请安装 send2trash (pip install send2trash) 模块"
                   f"，否则 File.delete(True) Folder.delete(True) 将无法使用")

from .decorator import param2path, timestamp
from .utils import P_TYPE

RENAME_TYPE = Literal['name', 'stem', 'suffix']
REPETITION_TYPE = Literal['a', 'all', 'p', 'part']
KEY_TYPE = RENAME_TYPE
C_TYPE = Literal['zip', 'tar']
F_TYPE = Union['File', 'Folder']
FOLDER_TYPE = Literal['', 'file', 'folder']


class File(object):
    """
    文件类
    """

    @param2path({'p': 1})
    def __init__(self, p: P_TYPE):
        # assert p.is_file(), f"不是一个文件：{p}"
        # 文件存在时 记录数据源地址
        self._source = p  # type: Path
        self._path_c = p  # type: Path # 记录文件地址
        self._path_str = str(p)
        # 获取文件类别
        self._type = ''  # 文件的 mime 类型
        self._suffix = ''  # 文件的真实后缀
        self._type_obj = None  # 类型对象

    def __str__(self):
        return self._path_str

    def __repr__(self):
        return f"File('{str(self)}')"

    @property
    def type_obj(self) -> file_types:
        """
        文件类型对象
        Returns:

        """
        if self._type_obj is None:
            self._type_obj = self.file_type(str(self.source))
        return self._type_obj

    @property
    def type(self) -> str:
        """
        类型 mime 名称
        Returns:

        """
        if self._type == '':
            self._type = self.type_obj.mime
        return self._type

    @property
    def suffix(self) -> str:
        """
        真实后缀
        Returns:

        """
        if self._suffix == '':
            self._suffix = self.type_obj.extension
        return self._suffix

    @property
    def source(self) -> Path:
        return self._source

    @property
    def p(self) -> Path:
        return self._path_c

    @property
    def size(self) -> int:
        """
        文件大小
        Returns:

        """
        return os.path.getsize(self._path_c)

    @property
    def is_valid(self) -> bool:
        """
        判断文件是否有效
        Returns:

        """
        return self.size > 0

    @staticmethod
    def file_type(p: str) -> file_types:
        """
        获取文件的类型
        Args:
            p: 文件地址

        Returns:

        """
        return guess(p)

    def rename(self, name: str, t: RENAME_TYPE = 'stem',
               is_update: bool = False) -> 'File':
        """
        文件更名
        Args:
            name: 新名称
            t: 更名类型 全名：name 简称：stem 后缀：suffix（注：要以.开头）
            is_update: 是否修改本地文件名

        Returns:

        """
        self._path_c = getattr(self.p, f"with_{t}")(name)  # type: Path
        self._path_str = str(self._path_c)
        if is_update:
            self._source.rename(self.p)
            self._source = self._path_c

        return self

    def delete(self, is_recycle: bool = True) -> None:
        """
        删除
        Args:
            is_recycle: 是否放入回收站
        Returns:

        """
        if is_recycle:
            if ret := send2trash(str(self)):
                raise Exception(ret)
            else:
                logger.info(f"文件（{self.p.name}）删除到回收站，{self}")
        else:
            self.p.unlink(True)
            logger.info(f"文件（{self.p.name}）永久删除，{self}")

    @param2path({'p': 1}, ['p'])
    def move(self, p: P_TYPE, is_copy: bool = True, is_rename: bool = True,
             is_cover: bool = False) -> 'File':
        """
        文件移动
        Args:
            p: 移动到的文件夹
            is_copy: 是否复制
            is_rename: 是否重命名
            is_cover: 重复是否覆盖

        Returns:

        """
        _out_p = p.joinpath(self.p.name)
        if _out_p.exists():
            _msg = f"文件({_out_p.name})已存在"
            if is_rename:
                _name_new = f"{_out_p.stem}_{int(time() * 1000)}"
                _out_p = _out_p.with_stem(_name_new)
                logger.info(f"{_msg}, 重命名为：{_name_new}")
            else:
                if not is_cover:
                    logger.warn(_msg)
                    return self

        if is_copy:
            shutil.copy(self._source, _out_p)
        else:
            self.source.replace(_out_p)
            self._source = _out_p  # 修改数据源
            self._path_c = _out_p
        return self


class Folder(object):
    @timestamp
    @param2path({'p': 1})
    def __init__(self, p: P_TYPE = '', d: Union[Sequence, Iterator] = None,
                 t: FOLDER_TYPE = '', is_r: bool = True, rule: str = '',
                 filter_callback: Callable[[F_TYPE], bool] = None,
                 map_callback: Callable[[F_TYPE], F_TYPE] = None,
                 ) -> None:
        """

        Args:
            p: 文件夹地址
            d: 初始数据
            t: 子对象类型
            is_r: 是否递归获取文件路径
            rule: 文件搜索的规则
            filter_callback: 过滤回调函数
            map_callback: 映射回调函数
        """
        self._path_c = p
        self._path_str = str(p)
        if p.exists():
            self._source = p
        self._data = None  # 数据容器
        self._recursion = is_r  # 递归
        self._data_type = t  # 数据类型
        self._d = d  # 初始数据
        self._rule = rule  # 文件搜索的规则
        self.filter_callback = filter_callback  # 过滤回调函数
        self.map_callback = map_callback  # 映射回调函数

    def __iter__(self) -> Generator[F_TYPE, None, None]:
        if self._data is None:
            yield from self._init_data()
        else:
            yield from self._data

    def __len__(self) -> int:
        if self._data is None:
            self._data = list(self._init_data())
        return len(self._data)

    def __getitem__(self, item) -> F_TYPE:
        if self._data is None:
            self._data = list(self._init_data())
        if isinstance(item, slice):
            return self.__class__(self._path_c, self._data[item])
        elif isinstance(item, int):
            return self._data[item]
        else:
            return self

    def __add__(self, other) -> 'Folder':
        if self._data is None:
            self._data = list(self._init_data())
        assert isinstance(other, self.__class__), f"数据类型不一致，无法相加"
        self._data += list(other)
        return self

    def __str__(self) -> str:
        return self._path_str

    def __repr__(self) -> str:
        return f"Folder({str(self)})"

    @property
    def source(self) -> Path:
        return self._source

    @property
    def p(self) -> Path:
        return self._path_c

    @property
    def size(self) -> int:
        """
        文件夹大小
        Returns:

        """
        return sum((_f.size for _f in self))

    @property
    def is_valid(self) -> bool:
        """
        判断文件夹是否有效
        Returns:

        """
        _folders = []
        # 判断文件
        for _f in self:
            if isinstance(_f, File):
                if _f.is_valid:
                    return True
            else:
                _folders.append(_f)
        # 判断文件夹
        for _f in _folders:
            if _f.is_valid:
                return True
        return False

    def _init_data(self) -> Generator[F_TYPE, None, None]:
        """
        数据初始化
        Returns:

        """
        self._data = []
        # 获取路径下所有文件路径对象
        if self._d is None:
            if self._source.is_file():
                if (f := self._add_f(File(self._source))) is not None:
                    yield f
            else:
                if self._recursion:
                    if self._rule == '':
                        self._rule = '*.*'
                    self._data_type = 'file'
                    for _p in self._path_c.rglob(self._rule):
                        if (f := self._add_f(File(_p))) is not None:
                            yield f
                else:
                    if self._rule == '':
                        self._rule = '*'
                    for _p in self._path_c.glob(self._rule):
                        if (f := self._add_f(self._get_f(_p))) is not None:
                            yield f
        else:
            for _p in iter(self._d):
                if isinstance(_p, Folder):
                    if self._data_type == '' or self._data_type == 'folder':
                        if (f := self._add_f(_p)) is not None:
                            yield f
                elif isinstance(_p, File):
                    if self._data_type == '' or self._data_type == 'file':
                        if (f := self._add_f(_p)) is not None:
                            yield f
                else:
                    if (f := self._add_f(self._get_f(_p))) is not None:
                        yield f
        logger.info(f"数据初始化，总数：{len(self)}")

    def _add_f(self, f: F_TYPE) -> Union[F_TYPE, None]:
        """
        添加 File Folder 对象到数据列表
        Args:
            f:

        Returns:

        """
        if f is None:
            return f
        # 执行映射
        if callable(self.map_callback):
            f = self.map_callback(f)
        # 执行过滤
        if callable(self.filter_callback):
            if not self.filter_callback(f):
                f = None
        # 追加数据
        if f is not None:
            self._data.append(f)
        return f

    @param2path({'p': 1})
    def _get_f(self, p: P_TYPE) -> Union[F_TYPE, None]:
        """
        设置数据内容
        Args:
            p:

        Returns:

        """
        _t = self._data_type
        f = None
        if p.is_dir():
            if _t == '' or _t == 'folder':
                f = Folder(p, t=_t, is_r=self._recursion)
        else:
            if _t == '' or _t == 'file':
                f = File(p)
        return f

    def delete(self, is_recycle: bool = True) -> None:
        """
        文件夹删除
        Args:
            is_recycle: 是否放入回收站

        Returns:

        """
        _msg = f"文件夹（{self.p.name}）"
        if is_recycle:
            send2trash(str(self))
            logger.info(f"{_msg}删除到回收站，{self}")
        else:
            for _f in self:
                _f.delete(is_recycle)
            try:
                self.source.rmdir()
                logger.info(f"{_msg}永久删除，{self}")
            except OSError:
                logger.warning(
                    f"文件夹删除失败，请使用"
                    f" Folder(r'{self._path_str}', is_r=False).delete(False)"
                    f" 重试"
                )

    @timestamp
    def copy(self, is_deep: bool = False) -> 'Folder':
        """
        拷贝对象
        Args:
            is_deep: 是否深拷贝

        Returns:
            Folder 对象
        """
        return deepcopy(self) if is_deep else copy(self)

    @timestamp
    def filter(self, call_back: Callable[[F_TYPE], bool]) -> 'Folder':
        """
        获取符合条件的所有文件或文件夹
        Args:
            call_back: 回调

        Returns:
            Folder 对象
        """
        _data = []
        for _i, _f in enumerate(self):
            if call_back(copy(_f)):
                _data.append(_f)
            if isinstance(_f, Folder):
                _data.append(_f.filter(call_back))
        self._data = _data
        return self

    @timestamp
    def map(self, call_back: Callable[[F_TYPE], Optional[F_TYPE]]) -> 'Folder':
        """
        遍历修改数据
        Args:
            call_back:

        Returns:

        """
        for _i, _f in self:
            self._data[_i] = call_back(copy(_f))
            if isinstance(_f, Folder):
                self._data[_i] = _f.map(call_back)
        return self

    def each(self, call_back: Callable[[F_TYPE], None]) -> 'Folder':
        """
        遍历所有数据
        Args:
            call_back:

        Returns:

        """
        for _f in self:
            call_back(copy(_f))
            if isinstance(_f, Folder):
                _f.each(call_back)

        return self

    def print(self) -> 'Folder':
        """
        遍历打印
        Returns:

        """
        self.each(lambda f: print(str(f)))
        return self

    @timestamp
    def valid(self) -> 'Folder':
        """
        过滤无效文件或文件夹
        Returns:

        """
        _list = []
        for _f in self:
            if isinstance(_f, Folder):
                _f = _f.valid()
            if _f.is_valid:
                _list.append(_f)

        self._data = _list

        return self

    @timestamp
    @param2path({'out_path': 1}, ['out_path'], 0)
    def compress(self, out_path: P_TYPE = r'.', name: str = '',
                 c_type: C_TYPE = 'zip') -> 'Folder':
        """
        所有文件添加到 zip 包
        Args:
            out_path: 压缩包输出地址
            name: 压缩包名称
            c_type: 压缩类型

        Returns:
            Folder 对象
        """
        if not name:
            name = 'package'

        def _compress():
            """
            压缩
            Returns:

            """
            if c_type.lower() == 'zip':
                out_path_c = out_path.joinpath(f"{name}.zip")
                with zipfile.ZipFile(str(out_path_c), mode='w',
                                     compression=zipfile.ZIP_STORED) as f:
                    for _f in self:
                        f.write(str(_f), arcname=_f.p.name)
                        logger.info(f"压缩: {_f}")
            else:
                raise Exception(f"不支持压缩类型：{c_type}")

        _compress()

        return self

    @timestamp
    @param2path({'out_path': 1}, ['out_path'], 0)
    def move(self, out_path: P_TYPE, is_copy: bool = True,
             is_rename: bool = True, is_cover: bool = False) -> 'Folder':
        """
        移动所有文件到指定路径下
        Args:
            out_path: 指定输出路径
            is_copy: 是否拷贝模式
            is_rename: 重名是否重命名
            is_cover: 是否覆盖

        Returns:
            Folder 对象
        """
        for _f in self:
            if isinstance(_f, File):
                _f.move(out_path, is_rename=is_rename, is_copy=is_copy,
                        is_cover=is_cover)
            else:
                _p_s = _f.source  # 数据源
                _out_p = out_path.joinpath(_f.p.name)  # 移动后的文件地址
                if is_cover or not _out_p.exists():
                    if is_copy:
                        shutil.copytree(_p_s, _out_p, dirs_exist_ok=True)
                    else:
                        _p_s.replace(_out_p)
            logger.info(f"文件移动：{_f}")

        return self

    @timestamp
    def rename(self, names: Union[Sequence[str], str, Callable[[File], str]],
               t: RENAME_TYPE = 'stem', is_update: bool = False) -> 'Folder':
        """
        文件夹下所有文件更名
        Args:
            names: 新的文件名序列
            t: 更名类型 全名：name 简称：stem 后缀：suffix（注：要以.开头）
            is_update: 是否修改本地文件名

        Returns:

        """
        if callable(names):
            for _i, _f in enumerate(self):
                self._data[_i] = _f.rename(names(_f), t, is_update)
        else:
            if isinstance(names, str):
                names = [names] * len(self)

            for _i, (_name, _f) in enumerate(zip(names, self)):
                if isinstance(_f, File):
                    # 更名
                    self._data[_i] = _f.rename(_name, t, is_update)

        return self
